Skip to content

fix: allow sheet closing text in SVS recipes - #377

Merged
rponeawa merged 2 commits into
hypit-ai:mainfrom
Arthur031221:fix/svs-root-close
Oct 8, 2026
Merged

rponeawa merged 2 commits into
hypit-ai:mainfrom
Arthur031221:fix/svs-root-close

Conversation

@Arthur031221

@Arthur031221 Arthur031221 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Authors who put a literal </sheet> in a Text Template prompt get SVS_TRAILING instead of rendered text. A prompt showing someone how to write SVS markup is one case.

The SVS parser found the first </sheet> in the source, even when it was inside a quoted recipe value. It now looks for the closing tag between recipes, so quoted text is read as part of its value. One regression test round-trips the value through the SVS formatter. Another parses the recipe into a Text Template and renders the prompt. Both failed with the original parser and pass with the fix.

Text after the closing tag, including a second </sheet>, is still rejected with SVS_TRAILING. A sheet whose recipes are complete but that has no closing tag reports SVS_ROOT_UNCLOSED, also when a quoted value contains </sheet>.

Checks run:

  • pnpm check
  • pnpm test (1,105 passed, 21 skipped, 0 failed)

Assisted by Claude/Codex.

Co-authored-by: Sharky Wang 65097782+rponeawa@users.noreply.github.com

@rponeawa

rponeawa commented Oct 8, 2026

Copy link
Copy Markdown
Member

Thanks for the fix and the clear write-up! The reported case works now, but lastIndexOf still guesses where the sheet ends instead of finding it, so malformed files now get misleading errors:

Input main This PR
Value contains "x </sheet> y", real close present SVS_TRAILING parses
Value contains "x </sheet> y", real close missing SVS_TRAILING SVS_TRAILING (should be SVS_ROOT_UNCLOSED)
</sheet> written twice SVS_TRAILING SVS_RULE "Expected a dotted recipe path"

The root cause is that parseSvs searches the raw text for </sheet> before parsing, and that search is the only scan in the file that is not quote-aware. A fix that addresses it directly is to drop the upfront search and detect the close tag inside the recipe loop:

cursor = openEnd + 1;
while (true) {
  cursor = skipSpace(text, cursor);
  if (cursor >= text.length) fail(sourceName, "SVS_ROOT_UNCLOSED", "Source is missing </sheet>.", source.length);
  if (text.startsWith("</sheet>", cursor)) break;
  // existing recipe parsing, with closingBrace scanning up to text.length
}
const end = cursor + "</sheet>".length;
if (text.slice(end).trim().length > 0) fail(sourceName, "SVS_TRAILING", "Only trivia may follow </sheet>.", end);

The loop only checks for </sheet> between recipes, and closingBrace already skips quoted text, so a quoted </sheet> is read as part of its value. All three inputs above then get the right result. One test for the quoted case is enough.

Would you like to update the PR yourself, or would you prefer that we push the change on top of it?

parseSvs searched the raw text for </sheet> before parsing, so a quoted
recipe value containing that text was either taken as the close or, with
lastIndexOf, guessed at. Check for the close tag only between recipes
instead, since closingBrace already skips quoted text. A missing close now
reports SVS_ROOT_UNCLOSED and a second close SVS_TRAILING, as on main.

Co-authored-by: Sharky Wang <65097782+rponeawa@users.noreply.github.com>
@Arthur031221

Copy link
Copy Markdown
Contributor Author

Pushed your change on top as its own commit, with you as co-author. Your three inputs now behave as you described: the quoted </sheet> with a real close parses, the same value with no real close gives SVS_ROOT_UNCLOSED, and a doubled </sheet> gives SVS_TRAILING, as on main.

I added the second and third to the existing test for the quoted case, and it fails on the lastIndexOf version. Malformed files can now report a different code than on main. For example, a recipe whose } only appears after </sheet> is still rejected, but with SVS_PROPERTY instead of SVS_TRAILING. I also updated the description where it no longer held, the closing tag sentence and the note about the duplicate tag error.

@rponeawa
rponeawa merged commit ef62b07 into hypit-ai:main Oct 8, 2026
@rponeawa

rponeawa commented Oct 8, 2026

Copy link
Copy Markdown
Member

Thanks for updating the PR so quickly and for adding the malformed-file cases to the test! The parser now finds the closing tag from the file structure instead of searching for it, which fixes the root cause. Merged, and it will ship in the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants